import { Position, posAdd, posIsEqual } from "./position";

export const roomSize = 50;

/**
 * 
 * @param {Number} x 
 * @param {Number} y 
 * @returns {Number}
 */
function posToIndex(x,y){
    return y * roomSize + x;
}
/**
 * 
 * @param {Number} i 
 * @returns {[Number, Number]}
 */
function indexToPos(i){
    return [i%roomSize, Math.floor(i/roomSize)];
}

export class StructureCluster{
    /**
     * 
     * @param {{x:Number, y:Number}} offset 
     */
    constructor(offset){
        this.offset = offset;
    }

    /**
     * 
     * @param {StructureCluster} obj 
     */
    set(obj){
        for(let name in obj){
            if(this[name]){
                continue;
            }
            this[name] = obj[name];
        }
    }

    /**
     * 
     * @abstract
     * @param {Room} room 
     * @returns {CreepActionReturnCode}
     */
    build(room, offset = {x:0,y:0}){}

    /**
     * 
     * @abstract
     * @param {Room} room 
     * @returns {Boolean}
     */
    isDone(room, offset = {x:0,y:0}){}

    /**
     * 
     * @abstract
     * @param {Room} room 
     * @returns {Boolean}
     */
    isConstructionSiteCreated(room, offset = {x:0,y:0}){}


    /**
     * 
     * @abstract
     * @param {Room} room 
     * @param {{ x: Number, y: Number }} offset
     * @returns {Boolean}
     */
    canFit(room, offset = {x:0, y:0}){}

    /**
     * 
     * @param {Room} room 
     * @returns {Boolean}
     */
    isCreated(room, offset = {x:0,y:0}){
        return this.isDone(room, offset) || this.isConstructionSiteCreated(room, offset);
    }

    /**
     * @abstract
     * @param {{ x: Number, y: Number; }} offset
     * @param {StructureCluster} cluster 
     * @returns {Boolean}
     */
    isEqual(cluster, offset = {x:0,y:0}){}
}

export class StructurePlan extends StructureCluster{
    /**
     * 
     * @param {StructureConstant} STRUCTURE_TYPE
     */
    constructor(STRUCTURE_TYPE){
        super({x:0,y:0});
        this.structureType = STRUCTURE_TYPE;
    }
    /**
     * 
     * @param {Room} room 
     * @returns {CreepActionReturnCode}
     */
    build(room, offset = {x:0,y:0}){
        if(!this.structureType){
            return OK;
        }
        offset = posAdd(this.offset, offset, room.name);
        return room.createConstructionSite(offset, this.structureType);
    }
    isConstructionSiteCreated(room, offset = {x:0,y:0}){
        if(!this.structureType){
            return true;
        }
        try{
            return new RoomPosition(this.offset.x+offset.x, this.offset.y+offset.y, room.name)
                .lookFor(LOOK_CONSTRUCTION_SITES)
                .filter((site)=>site.structureType === this.structureType).length > 0;
        }catch{
            return false;
        }
    }
    isDone(room, offset = {x:0,y:0}){
        if(!this.structureType){
            return true;
        }
        try{
            return new RoomPosition(this.offset.x+offset.x, this.offset.y+offset.y, room.name)
                .lookFor(this.structureType).length>0;
        }catch{
            return false;
        }
    }
    /**
     * 
     * @param {Room} room 
     * @param {{x:Number, y:Number}} offset 
     * @returns {Boolean}
     */
    canFit(room, offset = {x:0,y:0},test){
        let pos = room.getPositionAt(offset.x,offset.y);
        if(!pos){
            return false;
        }
        let looks;
        const overlapableTypes = [
            STRUCTURE_ROAD,
            STRUCTURE_RAMPART,
        ];
        const isOverlapable = (structureType)=>{
            return overlapableTypes.some((type)=>type === structureType);
        }
        let overlapable = isOverlapable(this.structureType);
        looks = pos
            .lookFor(LOOK_STRUCTURES)
            .filter((structure)=>{
                return structure.my !== false && 
                    (!overlapable || !isOverlapable(structure.structureType));
            });
        if(looks.length>0){
            return false;
        }
        let terrain = room.getTerrain();

        if(terrain.get(offset.x, offset.y) === TERRAIN_MASK_WALL){
            return false;
        }
        return true;
    }

    /**
     * 
     * @param {StructureCluster} cluster 
     * @param {{ x: Number, y: Number; }} offset
     * @returns {Boolean}
     */
    isEqual(cluster, offset = {x:0,y:0}){
        if(!(cluster instanceof StructurePlan)){
            return false;
        }
        return offset.x === 0 && offset.y === 0 && cluster.structureType === this.structureType;
    }
}

export class StructureMap extends StructureCluster{

    /**
     * 
     * @param {{x:Number, y:Number}} offset
     * @param {StructureCluster[][]} list
     */
    constructor(offset, list){
        super(offset);
        this.list = list;
    }

    /**
     * 
     * @param {(value:StructureCluster,i:Number)=>void} callback
     */
    forEach(callback){
        for(let i=0; i<this.list.length; i++){
            let plans = this.list[i];
            if(!plans){
                continue;
            }
            for(let plan of plans){
                callback(plan, i);
            }
        }
    }
    /**
     * 
     * @param {(value:StructureCluster,x:Number,y:Number)=>void} callback
     */
    forEachPos(callback){
        for(let i=0; i<this.list.length; i++){
            let plans = this.list[i];
            if(!plans){
                continue;
            }
            let [x,y] = indexToPos(i);
            x+=this.offset.x;
            y+=this.offset.y;
            for(let plan of plans){
                callback(plan, x, y);
            }
        }
    }

    build(room, offset = {x:0,y:0}){
        // console.log(indexToPos(1328));
        // console.log(JSON.stringify(offset), JSON.stringify(posAdd(this.offset, offset)), JSON.stringify(this.offset), this.list.length);
        // offset = posAdd(this.offset, offset);
        this.forEachPos((plan, x, y)=>{
            if(!plan.isCreated(room,offset)){
                plan.build(room,posAdd(offset, {x:x,y:y}));
            }
        });
        return OK;
    }

    plan(cluster, offset){
        if(cluster === this){
            return ERR_INVALID_ARGS;
        }
        offset = posAdd(this.offset, offset);
        let i=posToIndex(offset.x, offset.y);
        // console.log(this.list);
        if(!this.list[i]){
            this.list[i] = [cluster];
            return OK;
        }
        this.list[i].push(cluster);
        return OK;
    }

    /**
     * 
     * @param {(cluster:StructureCluster,i?:Number)=>Boolean} predicate 
     * @returns {Boolean}
     */
    test(predicate){
        try{
            this.forEach((cluster, i)=>{
                if(!predicate(cluster, i)){
                    throw new Error("not_err");
                }
            });
        }catch(e){
            if(e.message === "not_err"){
                return false;
            }
            throw e;
        }
        return true;
    }

    isDone(room, offset = {x:0,y:0}){
        return this.test((cluster)=>cluster.isDone(room,room,posAdd(this.offset, offset)));
    }
    isConstructionSiteCreated(room, offset = {x:0,y:0}){
        return this.test((cluster)=>cluster.isConstructionSiteCreated(room,room,posAdd(this.offset, offset)));
    }
    isCreated(room, offset = {x:0,y:0}){
        return this.test((cluster)=>cluster.isCreated(room,posAdd(this.offset, offset)));
    }
    canFit(room, offset = {x:0,y:0},test){
        let pos = {x:offset.x+this.offset.x,y:offset.y+this.offset.y};
        return this.test((cluster, i)=>{
            let [x,y] = indexToPos(i);
            return cluster.canFit(room, posAdd(pos, {x:x,y:y}), test);
        });
    }
    lookAt(x,y){
        let ans = this.list[posToIndex(x,y)];
        if(!ans){
            return [];
        }
        return ans;
    }
    set(obj){
        super.set(obj);
        let list = this.list;
        this.list = [];
        for(let i=0; i<list.length; i++){
            let objPlans = list[i];
            if(!(objPlans instanceof Array) || objPlans.length === 0){
                continue;
            }
            let plans = [];
            for(let objPlan of objPlans){
                if(!objPlan || (!objPlan.list && !objPlan.structureType)){
                    continue;
                }
                let plan;
                if(objPlan.list){
                    plan = new StructureMap();
                }else{
                    plan = new StructurePlan();
                }
                plan.set(objPlan);
                plans.push(plan);
            }
            this.list[i] = plans;
        }
    }

    /**
     * 
     * @param {StructureCluster} cluster 
     * @param {Position} offset
     * @returns {Boolean}
     */
    isEqual(cluster, offset = {x:0,y:0}){
        if(!(cluster instanceof StructureMap)){
            return false;
        }

        for(let i=0; i<this.list.length; i++){
            let plans = this.list[i];
            if(!plans){
                continue;
            }
            let [x,y] = indexToPos(i);
            x+=offset.x;
            y+=offset.y;
            let look = cluster.lookAt(x,y);
            if(!plans && look.length === 0){
                continue;
            }
            if(!plans || look.length === 0){
                return false;
            }
            if(look.length !== plans.length){
                return false;
            }
            for(let i=0; i<plans.length; i++){
                if(!plans[i] && !look[i]){
                    continue;
                }
                if(!plans[i] || !look[i]){
                    return false;
                }
                if(!(plans[i].isEqual(look[i],new Position(0,0)))){
                    return false;
                }
            }
        }
        return true;
    }

    flatten(){}//todo
}
/**
 * 
 * @param {StructureConstant[][][]} map 
 * @param {{x:Number, y:Number}} midPoint 
 * @returns {StructureCluster}
 */
export function parseStructures(map, midPoint){
    let list = [];
    let size = 0;

    for(let layer of map){
        if(!layer)
            continue;

        for(let y=0; y<roomSize; y++){
            let row = layer[y];
            if(!row){
                continue;
            }

            for(let x=0; x<roomSize; x++){
                let cell = row[x];
                if(cell === undefined){
                    continue;
                }

                let ind = posToIndex(x,y);
                if(!list[ind]) {
                    list[ind] = [];
                }

                list[ind].push(new StructurePlan(cell));
                size++;
            }
        }
    }
    if(size === 0){
        return undefined;
    }
    if(size === 1){
        return list[list.length-1][0];
    }
    return new StructureMap({x:-midPoint.x, y:-midPoint.y}, list);
}